 /*******************************************************************************
  * Copyright (c) 2003, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.framework.internal.core;

 import java.lang.reflect.Array ;
 import java.util.*;
 import org.eclipse.osgi.framework.debug.Debug;
 import org.eclipse.osgi.framework.util.Headers;
 import org.osgi.framework.*;

 /**
  * A registered service.
  *
  * The framework returns a ServiceRegistration object when a
  * {@link BundleContextImpl#registerService(String, Object, Dictionary) BundleContext.registerService}
  * method is successful. This object is for the private use of
  * the registering bundle and should not be shared with other bundles.
  * <p>The ServiceRegistration object may be used to update the properties
  * for the service or to unregister the service.
  *
  * <p>If the ServiceRegistration is garbage collected the framework may remove
  * the service. This implies that if a
  * bundle wants to keep its service registered, it should keep the
  * ServiceRegistration object referenced.
  */
 //TODO That's kind of big
 public class ServiceRegistrationImpl implements ServiceRegistration {
     /** Reference to this registration. */
     protected ServiceReferenceImpl reference;

     /** Internal framework object. */
     protected Framework framework;

     /** context which registered this service. */
     protected BundleContextImpl context;

     /** bundle which registered this service. */
     protected AbstractBundle bundle;

     /** list of contexts using the service.
      * Access to this should be protected by the registrationLock */
     protected ArrayList contextsUsing;

     /** service classes for this registration. */
     protected String [] clazzes;

     /** service object for this registration. */
     protected Object service;

     /** properties for this registration. */
     protected Properties properties;

     /** service id. */
     protected long serviceid;

     /** service ranking. */
     protected int serviceranking;

     /* internal object to use for synchronization */
     protected Object registrationLock = new Object ();

     /** The registration state */
     protected int state = REGISTERED;
     public static final int REGISTERED = 0x00;
     public static final int UNREGISTERING = 0x01;
     public static final int UNREGISTERED = 0x02;

     /**
      * Construct a ServiceRegistration and register the service
      * in the framework's service registry.
      *
      */
     protected ServiceRegistrationImpl(BundleContextImpl context, String [] clazzes, Object service, Dictionary properties) {
         this.context = context;
         this.bundle = context.bundle;
         this.framework = context.framework;
         this.clazzes = clazzes; /* must be set before calling createProperties. */
         this.service = service;
         this.contextsUsing = null;
         this.reference = new ServiceReferenceImpl(this);

         synchronized (framework.serviceRegistry) {
             serviceid = framework.getNextServiceId(); /* must be set before calling createProperties. */
             this.properties = createProperties(properties); /* must be valid after unregister is called. */

             if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
                 Debug.println("registerService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

             framework.serviceRegistry.publishService(context, this);
         }

         /* must not hold the registrations lock when this event is published */
         framework.publishServiceEvent(ServiceEvent.REGISTERED, reference);
     }

     /**
      * Unregister the service.
      * Remove a service registration from the framework's service
      * registry.
      * All {@link ServiceReferenceImpl} objects for this registration
      * can no longer be used to interact with the service.
      *
      * <p>The following steps are followed to unregister a service:
      * <ol>
      * <li>The service is removed from the framework's service
      * registry so that it may no longer be used.
      * {@link ServiceReferenceImpl}s for the service may no longer be used
      * to get a service object for the service.
      * <li>A {@link ServiceEvent} of type {@link ServiceEvent#UNREGISTERING}
      * is synchronously sent so that bundles using this service
      * may release their use of the service.
      * <li>For each bundle whose use count for this service is greater
      * than zero:
      * <ol>
      * <li>The bundle's use count for this service is set to zero.
      * <li>If the service was registered with a {@link ServiceFactory},
      * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
      * is called to release the service object for the bundle.
      * </ol>
      * </ol>
      *
      * @exception java.lang.IllegalStateException If
      * this ServiceRegistration has already been unregistered.
      * @see BundleContextImpl#ungetService
      */
     public void unregister() {
         synchronized (registrationLock) {
             if (state != REGISTERED) /* in the process of unregisterING */
             {
                 throw new IllegalStateException (Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
             }

             /* remove this object from the service registry */
             if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
                 Debug.println("unregisterService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

             synchronized (framework.serviceRegistry) {
                 framework.serviceRegistry.unpublishService(context, this);
             }

             state = UNREGISTERING; /* mark unregisterING */
         }

         /* must not hold the registrationLock when this event is published */
         framework.publishServiceEvent(ServiceEvent.UNREGISTERING, reference);

         /* we have published the ServiceEvent, now mark the service fully unregistered */
         service = null;
         state = UNREGISTERED;

         int size = 0;
         BundleContextImpl[] users = null;

         synchronized (registrationLock) {
             if (contextsUsing != null) {
                 size = contextsUsing.size();

                 if (size > 0) {
                     if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
                         Debug.println("unregisterService: releasing users"); //$NON-NLS-1$
 }
                     users = (BundleContextImpl[]) contextsUsing.toArray(new BundleContextImpl[size]);
                 }
             }
         }

         /* must not hold the registrationLock while releasing services */
         for (int i = 0; i < size; i++) {
             releaseService(users[i]);
         }

         contextsUsing = null;

         reference = null; /* mark registration dead */
         context = null;

         /* These fields must be valid after unregister is called:
          * properties
          */
     }

     /**
      * Returns a {@link ServiceReferenceImpl} object for this registration.
      * The {@link ServiceReferenceImpl} object may be shared with other bundles.
      *
      * @exception java.lang.IllegalStateException If
      * this ServiceRegistration has already been unregistered.
      * @return A {@link ServiceReferenceImpl} object.
      */
     public org.osgi.framework.ServiceReference getReference() {
         /* use reference instead of unregistered so that ServiceFactorys, called
          * by releaseService after the registration is unregistered, can
          * get the ServiceReference. Note this technically may voilate the spec
          * but makes more sense.
          */
         if (reference == null) {
             throw new IllegalStateException (Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
         }

         return (reference);
     }

     /**
      * Update the properties associated with this service.
      *
      * <p>The key "objectClass" cannot be modified by this method. It's
      * value is set when the service is registered.
      *
      * <p>The following steps are followed to modify a service's properties:
      * <ol>
      * <li>The service's properties are replaced with the provided properties.
      * <li>A {@link ServiceEvent} of type {@link ServiceEvent#MODIFIED}
      * is synchronously sent.
      * </ol>
      *
      * @param props The properties for this service.
      * Changes should not be made to this object after calling this method.
      * To update the service's properties this method should be called again.
      * @exception java.lang.IllegalStateException If
      * this ServiceRegistration has already been unregistered.
      *
      * @exception IllegalArgumentException If the <tt>properties</tt>
      * parameter contains case variants of the same key name.
      */
     public void setProperties(Dictionary props) {
         synchronized (registrationLock) {
             if (state != REGISTERED) /* in the process of unregistering */
             {
                 throw new IllegalStateException (Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
             }

             this.properties = createProperties(props);
         }

         /* must not hold the registrationLock when this event is published */
         framework.publishServiceEvent(ServiceEvent.MODIFIED, reference);
     }

     /**
      * Construct a properties object from the dictionary for this
      * ServiceRegistration.
      *
      * @param props The properties for this service.
      * @return A Properties object for this ServiceRegistration.
      */
     protected Properties createProperties(Dictionary props) {
         Properties properties = new Properties(props);

         properties.set(Constants.OBJECTCLASS, clazzes, true);
         properties.set(Constants.SERVICE_ID, new Long (serviceid), true);
         properties.setReadOnly();
         Object ranking = properties.getProperty(Constants.SERVICE_RANKING);

         serviceranking = (ranking instanceof Integer ) ? ((Integer ) ranking).intValue() : 0;

         return (properties);
     }

     /**
      * Get the value of a service's property.
      *
      * <p>This method will continue to return property values after the
      * service has been unregistered. This is so that references to
      * unregistered service can be interrogated.
      * (For example: ServiceReference objects stored in the log.)
      *
      * @param key Name of the property.
      * @return Value of the property or <code>null</code> if there is
      * no property by that name.
      */
     protected Object getProperty(String key) {
         synchronized (registrationLock) {
             return (properties.getProperty(key));
         }
     }

     /**
      * Get the list of key names for the service's properties.
      *
      * <p>This method will continue to return the keys after the
      * service has been unregistered. This is so that references to
      * unregistered service can be interrogated.
      * (For example: ServiceReference objects stored in the log.)
      *
      * @return The list of property key names.
      */
     protected String [] getPropertyKeys() {
         synchronized (registrationLock) {
             return (properties.getPropertyKeys());
         }
     }

     /**
      * Return the bundle which registered the service.
      *
      * <p>This method will always return <code>null</code> when the
      * service has been unregistered. This can be used to
      * determine if the service has been unregistered.
      *
      * @return The bundle which registered the service.
      */
     protected AbstractBundle getBundle() {
         if (reference == null) {
             return (null);
         }

         return (bundle);
     }

     /**
      * Get a service object for the using BundleContext.
      *
      * @param user BundleContext using service.
      * @return Service object
      */
     protected Object getService(BundleContextImpl user) {
         synchronized (registrationLock) {
             if (state == UNREGISTERED) /* service unregistered */
             {
                 return (null);
             }

             if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
                 Debug.println("getService[" + user.bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

             Hashtable servicesInUse = user.servicesInUse;

             ServiceUse use = (ServiceUse) servicesInUse.get(reference);

             if (use == null) {
                 use = new ServiceUse(user, this);

                 Object service = use.getService();

                 if (service != null) {
                     servicesInUse.put(reference, use);

                     if (contextsUsing == null) {
                         contextsUsing = new ArrayList(10);
                     }

                     contextsUsing.add(user);
                 }

                 return (service);
             } else {
                 return (use.getService());
             }
         }
     }

     /**
      * Unget a service for the using BundleContext.
      *
      * @param user BundleContext using service.
      * @return <code>false</code> if the context bundle's use count for the service
      * is zero or if the service has been unregistered,
      * otherwise <code>true</code>.
      */
     protected boolean ungetService(BundleContextImpl user) {
         synchronized (registrationLock) {
             if (state == UNREGISTERED) {
                 return (false);
             }

             if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
                 String bundle = (user.bundle == null) ? "" : user.bundle.toString(); //$NON-NLS-1$
 Debug.println("ungetService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

             Hashtable servicesInUse = user.servicesInUse;

             if (servicesInUse != null) {
                 ServiceUse use = (ServiceUse) servicesInUse.get(reference);

                 if (use != null) {
                     if (use.ungetService()) {
                         /* use count is now zero */
                         servicesInUse.remove(reference);

                         contextsUsing.remove(user);
                     }
                     return (true);
                 }
             }

             return (false);
         }
     }

     /**
      * Release the service for the using BundleContext.
      *
      * @param user BundleContext using service.
      */
     protected void releaseService(BundleContextImpl user) {
         synchronized (registrationLock) {
             if (reference == null) /* registration dead */
             {
                 return;
             }

             if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
                 String bundle = (user.bundle == null) ? "" : user.bundle.toString(); //$NON-NLS-1$
 Debug.println("releaseService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

             Hashtable servicesInUse = user.servicesInUse;

             if (servicesInUse != null) {
                 ServiceUse use = (ServiceUse) servicesInUse.remove(reference);

                 if (use != null) {
                     use.releaseService();
                     // contextsUsing may have been nulled out by use.releaseService() if the registrant bundle
 // is listening for events and unregisters the service
 if (contextsUsing != null)
                         contextsUsing.remove(user);
                 }
             }
         }
     }

     /**
      * Return the list of bundle which are using this service.
      *
      * @return Array of Bundles using this service.
      */
     protected AbstractBundle[] getUsingBundles() {
         synchronized (registrationLock) {
             if (state == UNREGISTERED) /* service unregistered */
                 return (null);

             if (contextsUsing == null)
                 return (null);

             int size = contextsUsing.size();
             if (size == 0)
                 return (null);

             /* Copy list of BundleContext into an array of Bundle. */
             AbstractBundle[] bundles = new AbstractBundle[size];
             for (int i = 0; i < size; i++)
                 bundles[i] = ((BundleContextImpl) contextsUsing.get(i)).bundle;

             return (bundles);
         }
     }

     /**
      * Return a String representation of this object.
      *
      * @return String representation of this object.
      */
     public String toString() {
         String [] clazzes = this.clazzes;
         int size = clazzes.length;
         StringBuffer sb = new StringBuffer (50 * size);

         sb.append('{');

         for (int i = 0; i < size; i++) {
             if (i > 0) {
                 sb.append(", "); //$NON-NLS-1$
 }
             sb.append(clazzes[i]);
         }

         sb.append("}="); //$NON-NLS-1$
 sb.append(properties);

         return (sb.toString());
     }

     /**
      * Hashtable for service properties.
      */
     static class Properties extends Headers {
         /**
          * Create a properties object for the service.
          *
          * @param props The properties for this service.
          */
         private Properties(int size, Dictionary props) {
             super(size);

             if (props != null) {
                 synchronized (props) {
                     Enumeration keysEnum = props.keys();

                     while (keysEnum.hasMoreElements()) {
                         Object key = keysEnum.nextElement();

                         if (key instanceof String ) {
                             String header = (String ) key;

                             setProperty(header, props.get(header));
                         }
                     }
                 }
             }
         }

         /**
          * Create a properties object for the service.
          *
          * @param props The properties for this service.
          */
         protected Properties(Dictionary props) {
             this((props == null) ? 2 : props.size() + 2, props);
         }

         /**
          * Get a clone of the value of a service's property.
          *
          * @param key header name.
          * @return Clone of the value of the property or <code>null</code> if there is
          * no property by that name.
          */
         protected Object getProperty(String key) {
             return (cloneValue(get(key)));
         }

         /**
          * Get the list of key names for the service's properties.
          *
          * @return The list of property key names.
          */
         protected synchronized String [] getPropertyKeys() {
             int size = size();

             String [] keynames = new String [size];

             Enumeration keysEnum = keys();

             for (int i = 0; i < size; i++) {
                 keynames[i] = (String ) keysEnum.nextElement();
             }

             return (keynames);
         }

         /**
          * Put a clone of the property value into this property object.
          *
          * @param key Name of property.
          * @param value Value of property.
          * @return previous property value.
          */
         protected synchronized Object setProperty(String key, Object value) {
             return (set(key, cloneValue(value)));
         }

         /**
          * Attempt to clone the value if necessary and possible.
          *
          * For some strange reason, you can test to see of an Object is
          * Cloneable but you can't call the clone method since it is
          * protected on Object!
          *
          * @param value object to be cloned.
          * @return cloned object or original object if we didn't clone it.
          */
         protected static Object cloneValue(Object value) {
             if (value == null)
                 return null;
             if (value instanceof String ) /* shortcut String */
                 return (value);
             if (value instanceof Number ) /* shortcut Number */
                 return value;
             if (value instanceof Character ) /* shortcut Character */
                 return value;
             if (value instanceof Boolean ) /* shortcut Boolean */
                 return value;

             Class clazz = value.getClass();
             if (clazz.isArray()) {
                 // Do an array copy
 Class type = clazz.getComponentType();
                 int len = Array.getLength(value);
                 Object clonedArray = Array.newInstance(type, len);
                 System.arraycopy(value, 0, clonedArray, 0, len);
                 return clonedArray;
             }
             // must use reflection because Object clone method is protected!!
 try {
                 return (clazz.getMethod("clone", null).invoke(value, null)); //$NON-NLS-1$
 } catch (Exception e) {
                 /* clone is not a public method on value's class */
             } catch (Error e) {
                 /* JCL does not support reflection; try some well known types */
                 if (value instanceof Vector)
                     return (((Vector) value).clone());
                 if (value instanceof Hashtable)
                     return (((Hashtable) value).clone());
             }
             return (value);
         }

         public synchronized String toString() {
             String keys[] = getPropertyKeys();

             int size = keys.length;

             StringBuffer sb = new StringBuffer (20 * size);

             sb.append('{');

             int n = 0;
             for (int i = 0; i < size; i++) {
                 String key = keys[i];
                 if (!key.equals(Constants.OBJECTCLASS)) {
                     if (n > 0)
                         sb.append(", "); //$NON-NLS-1$

                     sb.append(key);
                     sb.append('=');
                     Object value = get(key);
                     if (value.getClass().isArray()) {
                         sb.append('[');
                         int length = Array.getLength(value);
                         for (int j = 0; j < length; j++) {
                             if (j > 0)
                                 sb.append(',');
                             sb.append(Array.get(value, j));
                         }
                         sb.append(']');
                     } else {
                         sb.append(value);
                     }
                     n++;
                 }
             }

             sb.append('}');

             return (sb.toString());
         }
     }
 }

